const fs = require("fs");
const path = require("path");
const readFile = fs.readFile.bind(fs);
const PATH_QUERY_FRAGMENT_REGEXP = /^([^?#]*)(\?[^#]*)?(#.*)?$/;

function parsePathQueryFragment(resource) {
  //resource =./src/index.js?name=zhufeng#top
  let result = PATH_QUERY_FRAGMENT_REGEXP.exec(resource);
  return {
    path: result[1], //路径名 ./src/index.js
    query: result[2], //   ?name=zhufeng
    fragment: result[3], // #top
  };
}
function loadLoader(loaderObject) {
  let normal = require(loaderObject.path);
  loaderObject.normal = normal;
  loaderObject.pitch = normal.pitch;
  loaderObject.raw = normal.raw;
}
function convertArgs(args, raw) {
  if (raw && !Buffer.isBuffer(args[0])) {
    //如果这个loader需要 buffer,args[0]不是,需要转成buffer
    args[0] = Buffer.from(args[0], "utf8");
  } else if (!raw && Buffer.isBuffer(args[0])) {
    args[0] = args[0].toString("utf8");
  }
}

function createLoaderObject(loader) {
  let obj = {
    path: "", //当前loader的绝对路径
    query: "", //当前loader的查询参数
    fragment: "", //当前loader的片段
    normal: null, //当前loader的normal函数
    pitch: null, //当前loader的pitch函数
    raw: null, //是否是Buffer
    data: {}, //自定义对象 每个loader都会有一个data自定义对象
    pitchExecuted: false, //当前 loader的pitch函数已经执行过了,不需要再执行了
    normalExecuted: false, //当前loader的normal函数已经执行过了,不需要再执行
  };
  Object.defineProperty(obj, "request", {
    get() {
      return obj.path + obj.query + obj.fragment;
    },
    set(value) {
      let splittedRequest = parsePathQueryFragment(value);
      obj.path = splittedRequest.path;
      obj.query = splittedRequest.query;
      obj.fragment = splittedRequest.fragment;
    },
  });
  obj.request = loader;
  return obj;
}
function processResource(options, loaderContext, callback) {
  //重置loaderIndex 改为loader长度减1
  loaderContext.loaderIndex = loaderContext.loaders.length - 1;
  let resourcePath = loaderContext.resourcePath;
  //调用 fs.readFile方法读取资源内容
  options.readResource(resourcePath, function (err, buffer) {
    if (err) return callback(error);
    options.resourceBuffer = buffer; //resourceBuffer放的是资源的原始内容
    iterateNormalLoaders(options, loaderContext, [buffer], callback);
  });
}
function iterateNormalLoaders(options, loaderContext, args, callback) {
  if (loaderContext.loaderIndex < 0) {
    //如果正常的normal loader全部执行完了
    return callback(null, args);
  }
  let currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex];
  //如果说当这个normal已经执行过了,让索引减少1
  if (currentLoaderObject.normalExecuted) {
    loaderContext.loaderIndex--;
    return iterateNormalLoaders(options, loaderContext, args, callback);
  }
  let normalFn = currentLoaderObject.normal;
  currentLoaderObject.normalExecuted = true;
  convertArgs(args, currentLoaderObject.raw);
  runSyncOrAsync(normalFn, loaderContext, args, function (err) {
    if (err) return callback(err);
    let args = Array.prototype.slice.call(arguments, 1);
    iterateNormalLoaders(options, loaderContext, args, callback);
  });
}
function iteratePitchingLoaders(options, loaderContext, callback) {
  if (loaderContext.loaderIndex >= loaderContext.loaders.length) {
    return processResource(options, loaderContext, callback);
  }
  //获取当前的loader loaderIndex=0 loader1
  let currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex];
  if (currentLoaderObject.pitchExecuted) {
    loaderContext.loaderIndex++;
    return iteratePitchingLoaders(options, loaderContext, callback);
  }
  loadLoader(currentLoaderObject);
  let pitchFunction = currentLoaderObject.pitch;
  currentLoaderObject.pitchExecuted = true;
  if (!pitchFunction) {
    return iteratePitchingLoaders(options, loaderContext, callback);
  }
  runSyncOrAsync(
    pitchFunction, //要执行的pitch函数
    loaderContext, //上下文对象
    //这是要传递给pitchFunction的参数数组
    [
      loaderContext.remainingRequest,
      loaderContext.previousRequest,
      (loaderContext.data = {}),
    ],
    function (err, ...args) {
      if (args.length > 0) {
        //如果 args有值,说明这个pitch有返回值
        loaderContext.loaderIndex--; //索引减1,开始回退了
        iterateNormalLoaders(options, loaderContext, args, callback);
      } else {
        //如果没有返回值,则执行下一个loader的pitch函数
        iteratePitchingLoaders(options, loaderContext, callback);
      }
    }
  );
}
function runSyncOrAsync(fn, context, args, callback) {
  let isSync = true; //默认是同步
  let isDone = false; //是否完成,是否执行过此函数了,默认是false
  //调用context.async this.async 可以把同步把异步,表示这个loader里的代码是异步的
  context.async = function () {
    isSync = false; //改为异步
    return innerCallback;
  };
  const innerCallback = (context.callback = function () {
    isDone = true; //表示当前函数已经完成
    isSync = false; //改为异步
    callback.apply(null, arguments); //执行 callback
  });
  //第一次fn=pitch1,执行pitch1
  let result = fn.apply(context, args);
  //在执行pitch2的时候,还没有执行到pitch1 这行代码
  if (isSync) {
    isDone = true;
    return callback(null, result);
  }
}
exports.runLoaders = function (options, callback) {
  
  let resource = options.resource || "";
  //loaders的数组   loader的绝对路径的数组
  let loaders = options.loaders || [];
  //loader执行时候的上下文对象 这个对象将会成为loader执行的时候的this指针
  let loaderContext = {};
  //此方法用来读文件的
  let readResource = options.readResource || readFile;
  let splittedResource = parsePathQueryFragment(resource);
  let resourcePath = splittedResource.path; //文件路径
  let resourceQuery = splittedResource.query; //查询参数
  let resourceFragment = splittedResource.fragment; //片段
  let contextDirectory = path.dirname(resourcePath); //此文件所在的上下文目录
  //准备loader对象数组
  loaders = loaders.map(createLoaderObject);
  //要加载的资源的所在目录
  loaderContext.context = contextDirectory;
  loaderContext.loaderIndex = 0; //当前的 loader的索引
  loaderContext.loaders = loaders;
  loaderContext.resourcePath = resourcePath;
  loaderContext.resourceQuery = resourceQuery;
  loaderContext.resourceFragment = resourceFragment;
  loaderContext.async = null; //是一个方法,可以loader的执行从同步改成异步
  loaderContext.callback = null; //调用下一个loader
  //loaderContext.request代表要加载的资源 ./src/index.js路径里不包含loader
  Object.defineProperty(loaderContext, "resource", {
    get() {
      return (
        loaderContext.resourcePath +
        loaderContext.resourceQuery +
        loaderContext.resourceFragment
      );
    },
  });
  //request =loader1!loader2!loader3!resource.js
  Object.defineProperty(loaderContext, "request", {
    get() {
      return loaderContext.loaders
        .map((l) => l.request)
        .concat(loaderContext.resource)
        .join("!");
    },
  });
  //剩下的loader 从当前的下一个loader开始取,加上resource
  Object.defineProperty(loaderContext, "remainingRequest", {
    get() {
      return loaderContext.loaders
        .slice(loaderContext.loaderIndex + 1)
        .map((l) => l.request)
        .concat(loaderContext.resource)
        .join("!");
    },
  });
  //当前loader 从当前的loader开始取,加上resource
  Object.defineProperty(loaderContext, "currentRequest", {
    get() {
      return loaderContext.loaders
        .slice(loaderContext.loaderIndex)
        .map((l) => l.request)
        .concat(loaderContext.resource)
        .join("!");
    },
  });
  //之前loader
  Object.defineProperty(loaderContext, "previousRequest", {
    get() {
      return loaderContext.loaders
        .slice(0, loaderContext.loaderIndex)
        .map((l) => l.request);
    },
  });
  //当前loader的query
  Object.defineProperty(loaderContext, "query", {
    get() {
      let loader = loaderContext.loaders[loaderContext.loaderIndex];
      return loader.options || loader.query;
    },
  });
  //当前loader的data
  Object.defineProperty(loaderContext, "data", {
    get() {
      let loader = loaderContext.loaders[loaderContext.loaderIndex];
      return loader.data;
    },
  });
  let processOptions = {
    resourceBuffer: null, //最后我们会把loader执行的Buffer结果放在这里
    readResource,
  };
  iteratePitchingLoaders(processOptions, loaderContext, function (err, result) {
    if (err) {
      return callback(err, {});
    }
    callback(null, {
      result,
      resourceBuffer: processOptions.resourceBuffer,
    });
  });
};
